package com.googlecode.funclate.json;

import com.googlecode.totallylazy.Iterators;
import com.googlecode.totallylazy.Mapper;
import com.googlecode.totallylazy.time.Dates;

import java.util.Date;
import java.util.Iterator;
import java.util.Map;

import static com.googlecode.totallylazy.Appendables.append;

public class StreamingJson {
    public static final String SEPARATOR = ",";

    public static <A extends Appendable> A toJson(final Object o, final A appendable) {
        if (o instanceof CharSequence) {
            return toJsonCharSequence((CharSequence) o, appendable);
        }
        if (o instanceof CharSequence) {
            return toJsonCharSequence((CharSequence) o, appendable);
        }
        if (o instanceof Iterator) {
            return toJsonIterator((Iterator) o, appendable);
        }
        if (o instanceof Iterable) {
            return toJsonIterable((Iterable) o, appendable);
        }
        if (o instanceof Map) {
            return toJsonMap((Map) o, appendable);
        }
        if (o instanceof Map.Entry) {
            return toJsonMapEntry((Map.Entry) o, appendable);
        }
        if (o instanceof Void) {
            return toJsonVoid((Void) o, appendable);
        }
        if (o instanceof Number) {
            return toJsonNumber((Number) o, appendable);
        }
        if (o instanceof Date) {
            return toJsonDate((Date) o, appendable);
        }
        if (o instanceof Boolean) {
            return toJsonBoolean((Boolean) o, appendable);
        }
        if(o == null) {
            return toJsonNull(appendable);
        }
        return toJson(o.toString(), appendable);
    }

    private static <A extends Appendable> A toJsonCharSequence(final CharSequence charSequence, final A appendable) {
        return append(Strings.toString(charSequence), appendable);
    }

    public static <A extends Appendable> A toJsonIterator(final Iterator<?> iterator, final A appendable) {
        return iterate(iterator, appendable, "[", SEPARATOR, "]");
    }

    public static <A extends Appendable> A toJsonIterable(final Iterable<?> iterable, final A appendable) {
        if (iterable instanceof Map) return toJsonMap((Map) iterable, appendable);
        return toJsonIterator(iterable.iterator(), appendable);
    }

    public static <A extends Appendable> A toJsonMap(final Map<?, ?> map, final A appendable) {
        return iterate(map.entrySet().iterator(), appendable, "{", SEPARATOR, "}");
    }

    public static <A extends Appendable> A toJsonMapEntry(final Map.Entry<?, ?> entry, final A appendable) {
        return toJson(entry.getValue(), append(':', append(Strings.toString(String.valueOf(entry.getKey())), appendable)));
    }

    public static <A extends Appendable> A toJsonVoid(final Void aVoid, final A appendable) {
        return append("null", appendable);
    }

    public static <A extends Appendable> A toJsonNumber(final Number number, final A appendable) {
        return append(number.toString(), appendable);
    }

    public static <A extends Appendable> A toJsonDate(final Date date, final A appendable) {
        return append(Strings.quote(Dates.RFC3339withMilliseconds().format(date)), appendable);
    }

    public static <A extends Appendable> A toJsonBoolean(final Boolean bool, final A appendable) {
        return append(bool.toString(), appendable);
    }

    public static <A extends Appendable> A toJsonNull(final A appendable) {
        return append("null", appendable);
    }

    private static <A extends Appendable> A iterate(final Iterator<?> iterator, final A appendable, String start, String separator, String end) {
        return Iterators.appendTo(Iterators.map(iterator, new Mapper<Object, String>() {
            @Override
            public String call(Object o) throws Exception {
                toJson(o, appendable);
                return "";
            }
        }), appendable, start, separator, end);
    }
}